package com.chinackts.customsdeclarations.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chinackts.auth.model.MenuType;
import com.chinackts.auth.model.RdTime;
import com.chinackts.auth.repository.redis.SocialCreditCodeRepository;
import com.chinackts.auth.service.HeaderStoreService;
import com.chinackts.auth.service.MenuTypeService;
import com.chinackts.auth.service.RdTimeService;
import com.chinackts.common.exception.ApplicationException;
import com.chinackts.common.filestorage.EPortFileStorageService;
import com.chinackts.common.filestorage.StorageModule;
import com.chinackts.common.log.aspect.CusDecReqLog;
import com.chinackts.common.log.model.CusDecDataType;
import com.chinackts.common.okhttp.OkHttpRequest;
import com.chinackts.common.utils.ErrorMsgUtil;
import com.chinackts.common.utils.SpringUtil;
import com.chinackts.customsdeclarations.dto.CustomsDeclarationsListDto;
import com.chinackts.customsdeclarations.model.DecModType;
import com.chinackts.customsdeclarations.query.CustomsDeclarationsDetailsQuery;
import com.chinackts.customsdeclarations.query.CustomsDeclarationsListQuery;
import com.chinackts.customsdeclarations.query.CustomsDeclarationsPageListQuery;
import com.chinackts.customsdeclarations.repository.redis.DecDetailLastReqTimeRepository;
import com.chinackts.customsdeclarations.repository.redis.DecDetailSignRepository;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.zhoujianhui.filestorage.model.FileContent;
import org.zhoujianhui.filestorage.model.FileMetaInfo;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;

/**
 * @author Lianhong
 * @since 2021/11/18
 */
@Service
public class CustomsDeclarationsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomsDeclarationsService.class);

    //报关单列表
    private static final String CUSTOMS_DECLARATIONS_LIST_URL = "https://i.chinaport.gov.cn/rtxwebserver/sw/rtx/billDownload/queryEntryIdBill";
    //报关单明细
    private static final String CUSTOMS_DECLARATIONS_DETAILS_URL = "https://i.chinaport.gov.cn/rtxwebserver/sw/rtx/decDetail/viewDetail";
    //报关单PDF
    private static final String CUSTOMS_DECLARATIONS_PDF_URL = "https://i.chinaport.gov.cn/rtxwebserver/sw/rtx/decDetail/preview/%s,1";

    @Resource(name = "proxyOkHttpRequest")
    private OkHttpRequest request;
    @Resource
    private HeaderStoreService headerStoreService;
    @Resource
    private MenuTypeService menuTypeService;
    @Resource
    private CustomsDeclarationsStoreService customsDeclarationsStoreService;
    @Resource
    private RdTimeService rdTimeService;
    @Resource
    private DecDetailSignRepository decDetailSignRepository;
    @Resource
    private EPortFileStorageService ePortFileStorageService;
    @Resource
    private SocialCreditCodeRepository socialCreditCodeRepository;
    @Resource
    private RevokeListService revokeListService;
    @Resource
    private DecDetailLastReqTimeRepository decDetailLastReqTimeRepository;

    public CustomsDeclarationsListDto list(String id, CustomsDeclarationsListQuery query) {
        if (StringUtils.isNotBlank(query.getSign())) {
            headerStoreService.addHeader_30001(id, query.getSign());
        }
        //请求rtx rdtime，并添加至请求头
        rdTimeService.rtxRdTimeRequestAndStore(id);
        headerStoreService.setRdTimeHeader(id, RdTime.Rtx);
        //解密js
        menuTypeService.menuTypeRequestAndStore(id, MenuType.Rtx);
        String decType = query.getDecType();

        //报关单类型为空是，默认三种类型的报关单都采集
        if (StringUtils.isNotBlank(decType)) return this.customsDeclarationsList(id, query);
        //结关报关单
        CustomsDeclarationsListDto customsClearance = this.queryForCustomsClearance(id, query);
        //报关单退税联
//        CustomsDeclarationsListDto taxRefundCopy = this.queryForTaxRefundCopy(id, query);
        //启运港退税报关单
        CustomsDeclarationsListDto portOfDeparture = this.queryForPortOfDeparture(id, query);

        CustomsDeclarationsListDto response = new CustomsDeclarationsListDto();
        //结关报关单 与 报关单退税联 合并，以结关报关单为主
//        if (ObjectUtils.isNotEmpty(customsClearance)) {
//            response = customsClearance.union(taxRefundCopy);
//        }
        response = customsClearance;
        //启运港退税报关单 与 合并后的报关单 再次合并，以启运港退税报关单为主
        if (ObjectUtils.isNotEmpty(portOfDeparture)) {
//            response = portOfDeparture.union(response);
            response = customsClearance.union(portOfDeparture);
        }
        return response;
    }

    /**
     * 查询结关报关单
     */
    private CustomsDeclarationsListDto queryForCustomsClearance(String id, CustomsDeclarationsListQuery query) {
        return this.customsDeclarationsList(id, query.customsClearanceDecType());
    }

    /**
     * 查询启运港退税报关单
     */
    private CustomsDeclarationsListDto queryForPortOfDeparture(String id, CustomsDeclarationsListQuery query) {
        if (!query.isPortOfDeparture()) return null;
        return this.customsDeclarationsList(id, query.portOfDepartureDecType());
    }

    /**
     * 查询报关单退税联
     */
    private CustomsDeclarationsListDto queryForTaxRefundCopy(String id, CustomsDeclarationsListQuery query) {
        return this.customsDeclarationsList(id, query.taxRefundCopyDecType());
    }

    /**
     * 分页查询报关单列表
     */
    public CustomsDeclarationsListDto pageList(String id, CustomsDeclarationsPageListQuery query) {
        if (StringUtils.isNotBlank(query.getSign())) {
            headerStoreService.addHeader_30001(id, query.getSign());
        }
        //请求rtx rdtime，并添加至请求头
        rdTimeService.rtxRdTimeRequestAndStore(id);
        headerStoreService.setRdTimeHeader(id, RdTime.Rtx);
        //解密js
        menuTypeService.menuTypeRequestAndStore(id, MenuType.Rtx);
        CustomsDeclarationsService self = SpringUtil.getBean(CustomsDeclarationsService.class);
        JSONObject queries = query.buildPageQueries();
        CustomsDeclarationsListDto response = self.queryList(id, queries);
        response.getRows().forEach(item -> item.put("decType", query.getDecType()));
        return response;
    }

    private CustomsDeclarationsListDto customsDeclarationsList(String id, CustomsDeclarationsListQuery query) {
        JSONObject queries = query.buildQueries();
        int pageNo = 1;
        int pageSize = 500;
        queries.put("pageSize", pageSize);
        int totalPage;
        CustomsDeclarationsListDto response = new CustomsDeclarationsListDto();
        CustomsDeclarationsService self = SpringUtil.getBean(CustomsDeclarationsService.class);
        do {
            queries.put("curPage", pageNo);
            CustomsDeclarationsListDto list = self.queryList(id, queries);
            response.addRows(list.getRows());
            response.setTotal(list.getTotal());
            totalPage = (list.getTotal() + pageSize - 1) / pageSize;
            pageNo++;
        } while (pageNo <= totalPage);

        if (Objects.isNull(response.getRows())) return response;
        response.getRows().forEach(item -> {
            if (Objects.isNull(item)) return;
            item.put("decType", query.getDecType());
            String entryId = item.getString("entryId");
            if (StringUtils.isBlank(entryId)) return;
            item.put("fileStored", customsDeclarationsStoreService.stored(entryId));
        });
        return response;
    }

    @CusDecReqLog(type = CusDecDataType.CUSTOMS_DECLARATION_LIST, id = "#{#id}", urlConstants = CustomsDeclarationsService.CUSTOMS_DECLARATIONS_LIST_URL)
    public CustomsDeclarationsListDto queryList(String id, JSONObject queries) {
        String socialCreditCode = socialCreditCodeRepository.getSocialCreditCode(id);
        LOGGER.info("{} - orgCode: {}  开始采集列表", id, socialCreditCode);
        try {
            Thread.sleep(6 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String response = request.post(CUSTOMS_DECLARATIONS_LIST_URL, queries.toJSONString());
        if (StringUtils.isBlank(response)) {
            throw new ApplicationException("报关单数据采集失败，请重试");
        }
        response = menuTypeService.decode(id, response, MenuType.Rtx);
        JSONObject data = JSON.parseObject(response);

        String responseCode = data.getString("responseCode");

        if ("30003".equals(responseCode)) {
            try {
                LOGGER.warn("{} - 30003响应码，60s后重新采集列表，{}", id, queries);
                Thread.sleep(60 * 1000);
                LOGGER.warn("{} - 30003响应码，开始重新采集列表，{}", id, queries);
                response = request.post(CUSTOMS_DECLARATIONS_LIST_URL, queries.toJSONString());
                if (StringUtils.isBlank(response)) {
                    throw new ApplicationException("报关单数据采集失败，请重试");
                }
                response = menuTypeService.decode(id, response, MenuType.Rtx);
                data = JSON.parseObject(response);
                responseCode = data.getString("responseCode");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        if ("30001".equals(responseCode)) {
            throw new ApplicationException("30001", "关单列表采集出错，请验签后重新尝试采集");
        }
        if (!"100".equals(responseCode)) {
            LOGGER.warn("{} - 关单列表采集出错->Request: {}", id, queries.toJSONString());
            LOGGER.warn("{} - 关单列表采集出错->Response: {}", id, response);
        }
        JSONObject responseData = data.getJSONObject("responseData");
        CustomsDeclarationsListDto list = new CustomsDeclarationsListDto();
        if (responseData == null) return list;
        list = JSON.toJavaObject(responseData, CustomsDeclarationsListDto.class);
        List<JSONObject> rows = list.getRows();
        if (CollectionUtils.isEmpty(rows)) return list;
        List<String> entryIds = customsDeclarationsStoreService.exists(rows);

        LOGGER.info("{} - 共{}条数据，其中{}条数据已入库", id, rows.size(), entryIds.size());
        rows.forEach(item -> {
            String entryId = item.getString("entryId");
            boolean stored = entryIds.contains(entryId);
            item.put("stored", stored);
            if (!stored) return;
            customsDeclarationsStoreService.update(entryId, new JSONObject() {{
                put("decType", queries.getString("decType"));
                put("flagDec", item.getString("flagDec"));
                put("opMode", item.getString("opMode"));
            }});
        });
        return list;
    }

    @CusDecReqLog(type = CusDecDataType.CUSTOMS_DECLARATION, id = "#{#id}", urlConstants = CustomsDeclarationsService.CUSTOMS_DECLARATIONS_DETAILS_URL)
    public JSONObject details(String id, String entryId, CustomsDeclarationsDetailsQuery query) {
        String socialCreditCode = socialCreditCodeRepository.getSocialCreditCode(id);
        LOGGER.info("{} - orgCode: {} ******************************* {} **********************************", id, socialCreditCode, entryId);
        //请求rtx rdtime，并添加至请求头
        rdTimeService.rtxRdTimeRequestAndStore(id);
        headerStoreService.setRdTimeHeader(id, RdTime.Rtx);

        if (query.isRegather() || "2".equals(query.getDecType())) {
            LOGGER.info("{} - 重新请求官网采集退税联明细数据并返回【regather: {}, decType(1-结关/2-启运港): {}】", id, query.isRegather(), query.getDecType());
            JSONObject responseData = this.customsDeclarationsDetail(id, entryId, query, socialCreditCode);
            JSONObject cardHead = new JSONObject() {{
                put("entryId", entryId);
                put("decType", query.getDecType());
                put("flagDec", query.getFlagDec());
                put("opMode", query.getOpMode());
                put("socialCreditCode", socialCreditCode);
                put("customsDeclaration", responseData);
            }};
            if (!"2".equals(query.getDecType())) {
//                customsDeclarationsStoreService.delete(entryId);
                customsDeclarationsStoreService.save(cardHead);
            }
            return responseData;
        }

        //报关单对应修撤单类型（1-修单 2-撤单）
        DecModType modType = revokeListService.revokeListModType(id, entryId);
        if (DecModType.Modification.equals(modType)) {
            LOGGER.warn("{} - 出口报关单号（{}）存在`修改申请`的修撤单数据，删除已入库的出口报关单，重新请求保存", id, entryId);
            customsDeclarationsStoreService.delete(entryId);
        } else if (DecModType.Revocation.equals(modType)) {
            LOGGER.warn("{} - 出口报关单号（{}）存在`撤销申请`的修撤单数据，删除已入库的出口报关单", id, entryId);
            customsDeclarationsStoreService.delete(entryId);
            return new JSONObject();
        } else {
            //根据 出口报关单号 判断是否已存在关单数据
            boolean exists = customsDeclarationsStoreService.exists(entryId, query.getiEDate());
            //海关数据库中存在此报关单，则查询海关数据库并返回
            if (exists) {
                JSONObject result = customsDeclarationsStoreService.get(entryId);
                if (result != null) {
                    LOGGER.info("{} - 出口报关单号（{}）已存在于海关数据库，提取数据并响应。", id, entryId);
                    return result;
                }
                LOGGER.info("{} - 出口报关单号（{}）已存在于海关数据库，但数据为空，需重新采集。", id, entryId);
                customsDeclarationsStoreService.delete(entryId);
            }
        }
        //海关数据库中不存在此报关单，则查询电子口岸，并保存数据
        LOGGER.info("{} - 出口报关单号（{}）不存在于海关数据库，开始采集 ···", id, entryId);
        JSONObject responseData = this.customsDeclarationsDetail(id, entryId, query, socialCreditCode);
        JSONObject cardHead = new JSONObject() {{
            put("entryId", entryId);
            put("decType", query.getDecType());
            put("flagDec", query.getFlagDec());
            put("opMode", query.getOpMode());
            put("socialCreditCode", socialCreditCodeRepository.getSocialCreditCode(id));
            put("customsDeclaration", responseData);
        }};
        customsDeclarationsStoreService.save(cardHead);
        //文件是否已存储
        boolean stored = customsDeclarationsStoreService.stored(entryId);
        if (stored) {
            //响应已存储文件
            JSONObject customsDeclarationFile = customsDeclarationsStoreService.storedFile(entryId);
            String storedFile = customsDeclarationFile.getString("file");
            String[] storedFileArr = storedFile.split("/");
            ePortFileStorageService.delete(StorageModule.CUS, storedFileArr[storedFileArr.length - 1]);
            customsDeclarationsStoreService.delFile(entryId);
        }
        return responseData;
    }

    private JSONObject customsDeclarationsDetail(String id, String entryId, CustomsDeclarationsDetailsQuery query, String socialCreditCode) {
        try {
            long rt = decDetailLastReqTimeRepository.getRemainingTime(socialCreditCode);
            LOGGER.info("电子口岸报关单明细采集开始（{}），距离上一次明细采集，剩余时间为 - {} ms", entryId, rt);
            //明细采集间隔6秒查询
            Thread.sleep(rt);
            JSONObject params = new JSONObject();
            params.put("entryId", entryId);
            params.put("decType", query.getDecType());
            if (StringUtils.isNotEmpty(query.getEncryptStr())) params.put("encryptStr", query.getEncryptStr());

            //根据统一编号获取数据库中的加签字符串，添加到请求头部
            String storeSign = decDetailSignRepository.getDetailSign(entryId);
            if (StringUtils.isNotBlank(storeSign)) {
                headerStoreService.addHeader_30001(id, storeSign);
            }

            if (StringUtils.isNotBlank(query.getSign())) {
                headerStoreService.addHeader_30001(id, query.getSign());
            }
            String response = request.post(CUSTOMS_DECLARATIONS_DETAILS_URL, params.toJSONString());
            //des 解密
            String desResponse = menuTypeService.decode(id, response, MenuType.Rtx);

            JSONObject result;
            try {
                result = JSON.parseObject(desResponse);
            } catch (Exception e) {
                LOGGER.error("{} - 报关单明细数据解析失败: {}", id, response, e);
                throw new ApplicationException("9991", "报关单明细数据解析失败，请重试。");
            }

            if (result == null) throw new ApplicationException("9991", "未采集到报关单明细，请重试。");

            String responseCode = result.getString("responseCode");

            if (StringUtils.equalsAny(responseCode, "30003", "0")) {
                try {
                    LOGGER.warn("{} - 【{}】错误响应码：{}，60s后重新采集明细", id, entryId, responseCode);
                    Thread.sleep(60 * 1000);
                    LOGGER.warn("{} - 【{}】错误响应码：{}，开始重新采集明细，{}", id, entryId, responseCode, JSON.toJSONString(query));

                    response = request.post(CUSTOMS_DECLARATIONS_DETAILS_URL, params.toJSONString());
                    //des 解密
                    desResponse = menuTypeService.decode(id, response, MenuType.Rtx);
                    result = JSON.parseObject(desResponse);
                    responseCode = result.getString("responseCode");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    LOGGER.error("{} - 重新采集报关单明细数据解析失败: {}", id, response, e);
                    throw new ApplicationException("9991", "报关单明细数据解析失败，请重试。");
                }
            }
            if ("30001".equals(responseCode))
                throw new ApplicationException("30001", String.format("%s；进出口货物明细采集出错，请验签后重新尝试采集", entryId));
            if (!"100".equals(responseCode)) {
                LOGGER.error("报关单号为{}的关单查询失败 -> request : {}", entryId, JSON.toJSONString(query));
                LOGGER.error("报关单号为{}的关单查询失败 -> response : {}", entryId, desResponse);
                throw new ApplicationException("30001", String.format("%s；进出口货物明细采集出错，请验签后重新尝试采集", entryId));
            }
            long ct = decDetailLastReqTimeRepository.saveCurrentTime(socialCreditCode);
            LOGGER.info("{} 电子口岸报关单明细采集结束，存储采集时间 - {} ms", entryId, ct);

            if (StringUtils.isNotBlank(query.getSign()))
                decDetailSignRepository.saveDetailSign(entryId, query.getSign());
            return result.getJSONObject("responseData");
        } catch (Exception e) {
            LOGGER.error("{} - 采集报关单号为 {} 的数据异常", id, entryId, e);
            throw new RuntimeException(String.format("采集报关单号为 %s 的数据异常：%s", entryId, ErrorMsgUtil.getErrorMsg(e)));
        } finally {
            headerStoreService.removeHeader_30001(id);
        }
    }

    public void customsDeclarationsPdf(String id, String entryId, HttpServletResponse response) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            //文件是否已存储
            boolean stored = customsDeclarationsStoreService.stored(entryId);
            outputStream = response.getOutputStream();
            if (stored) {
                //响应已存储文件
                JSONObject customsDeclarationFile = customsDeclarationsStoreService.storedFile(entryId);
                String storedFile = customsDeclarationFile.getString("file");
                String storageTime = customsDeclarationFile.getString("storageTime");
                String[] storedFileArr = storedFile.split("/");
                //存储时间在 2024-07-04 之前的报关单附件，删除后重新存储
                if ("2024-07-04 00:00:00".compareTo(storageTime) > 0) {
                    ePortFileStorageService.delete(StorageModule.CUS, storedFileArr[storedFileArr.length - 1]);
                    customsDeclarationsStoreService.delFile(entryId);
                } else {
                    FileContent content = ePortFileStorageService.load(StorageModule.CUS, storedFileArr[storedFileArr.length - 1]);
                    inputStream = content.getData();
                    IOUtils.copy(inputStream, outputStream);
                    return;
                }
            }
            byte[] bytes = request.get(String.format(CUSTOMS_DECLARATIONS_PDF_URL, entryId));
            if (bytes == null) {
                throw new ApplicationException("未下载到报关单PDF，请重试。");
            }
            //保存文件
            FileMetaInfo fileInfo = ePortFileStorageService.store(StorageModule.CUS, String.format("%s.pdf", entryId), bytes);
            customsDeclarationsStoreService.saveFile(StorageModule.CUS, entryId, fileInfo.getStorageName());
            inputStream = new ByteArrayInputStream(bytes);
            IOUtils.copy(inputStream, outputStream);
        } catch (Exception e) {
            LOGGER.error("报关单PDF下载失败", e);
            throw new ApplicationException("报关单PDF下载失败");
        } finally {
            try {
                if (outputStream != null) outputStream.flush();
                IOUtils.closeQuietly(inputStream);
                IOUtils.closeQuietly(outputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
